"""
TODO: implement error state that causes exit (take the red pill)

Agent base class.

This class provides some basic "skills"
- Ability to save and restore the "data" property into the //database/agent
  table. The data property is a dictionary.

Each agent's name needs to be unique in the //database/agent table.
Agent's name size is limited by the definition of the name field in the
database.Agent class.

Agent's data is a dictionary and will be *pickle'd*, so all stored objects
in the data property must be able to be pickle'd.

TODO: update this: talk about the cycle and transfer the docstring to the class.
See also: database.py

"""
import sys
import time
from system import database
from system import navalaprocess
import system.navalasystem as na

AS_NONE = -1 #
AS_UNINITIALIZED = 0 # state prior to initialization
AS_INITIAL = 1 # state after initialization
AS_END = -9999 # End state (see agent.RunUntilTheEnd)


class Agent(navalaprocess.NavalaProcess):
    ## State variables
    data = {"state": AS_UNINITIALIZED} #: Dictionary containing all persistent
                                       #: values of interest
    flagBooted = False #: TODO: this should have an underscore
    __counter = 0 #: TODO: What is this?
    
    def __str__(self):
        return "%s %s" % (self.name, self.__class__.__name__)
    
    ## /\ Called from parent process
    ###########################################################################
    ## \/ Called from child process
    
    def run_(self):
        """Repeatedly calls Cycle() until state reaches AS_END."""
        self.Boot()
        self.LoadData()
        while True:
            self.ReadQueue()
            if self.data["state"] == AS_END:
                # When reaches AS_END, sets to restart next time
                self.SetState(AS_INITIAL)
                self.SaveData()
                self.logger.info("%s Finished the procect with END state %s" % (self.dash, self.dash))
                break
            if self.flagExit:
                self.logger.info("%s Forced to exit %s" % (self.dash, self.dash))
                break
            self.Cycle(flagLoadData=False)
            
    def SetState(self, x):
        self.data["state"] = x
        self.logger.debug("State changed to %d" % x)
        
    def GetState(self):
        return self.data["state"]
        
    def Boot(self):
        """Executed right after the agent instance has been created.
        
        This routine loads/initializes the data property, then calls Boot_()
        """
        
        try:
            self.LoadData()
        except database.KeyNotFound:
            self.InitializeData()
            self.SaveData()          
        self.Boot_()
        self.flagBooted = True
        
    def Boot_(self):
        """Left for descendants to perform some specific task at the end of boot"""
        
    def InitializeData(self):
        """Initialization of the data property.
       
        This routine runs only once, that is, when the agent is instantiated
        for the first time.
        
        Left to descendants to assign data initial/default values.
        """

    def SaveData(self):
        """Writes the data property into the database.
        
        The key to the //database/agent table is the agent's name. Data is
        serialized using pickle.
        """
        na.vala.db.agent_Update(name=self.name, data=self.data)
        
    def LoadData(self):
        """
        Unserializes the data property from the //database/agent table.
        
        """
        temp = na.vala.db.agent_GetData(name=self.name)
        if temp is None:
            raise NoData("Data not found in database for agent \"%s\"" %
                self.name)
        self.data = temp
    
    def Cycle(self, flagLoadData=True):
        """State-dependent activity.
        
        The state AS_UNITIALIZED is checked here and acted upon;
        Otherwise, passes the turn to Cycle_().
        
        """
        self.logger.info("Cycle %d" % self.__counter)
        if flagLoadData:
            self.LoadData()
        state = self.data["state"]

        if state == AS_UNINITIALIZED:
            self.InitializeData()
            self.SetState(AS_INITIAL)
        elif self.flagReinitiate:
            self.flagReinitiate = False
            self.SetState(AS_INITIAL)
        else:
            self.Cycle_()

        self.SaveData()
        self.__counter += 1
        

######################################################################################
## Errors

class NoData(Exception):
    pass
